/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package milkyway.hightech.banking;
import milkyway.hightech.admin.BankAdministrationView;
import milkyway.hightech.conditions.GiroAccountConditions;
import milkyway.hightech.conditions.InvestmentAccountConditions;
import milkyway.hightech.exceptions.TransactionNotAllowedException;
import milkyway.hightech.money.Currency;
import milkyway.hightech.money.Money;
import milkyway.hightech.timer.TimeEmitter;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Logger;
import milkyway.hightech.b2b.B2B;
import milkyway.hightech.b2b.BankRegistry;
import milkyway.hightech.comparators.SortByAmount;
import milkyway.hightech.comparators.SortByDate;
import milkyway.hightech.comparators.SortByName;
import milkyway.hightech.comparators.SortOrder;
import milkyway.hightech.conditions.ConditionTypes;
import milkyway.hightech.conditions.Conditions;
import milkyway.hightech.customer.BankCustomerView;
import milkyway.hightech.entry.Entry;
import milkyway.hightech.exceptions.SystemNotAvailableException;
/**
 *
 * @author michael
 */
public class BankImpl implements BankCustomerView, BankAdministrationView, Observer, B2B
{
    private final String bankName;
    
    /**
     * the initial number of giro accounts
     */
    public int accountNumber = 100000;

    /**
     *
     */
    private Map<Integer, Account> accounts = new HashMap<Integer, Account>();
    
    /**
     * 
     */
    private EnumMap<ConditionTypes, Conditions> conditions = new EnumMap<ConditionTypes, Conditions>(ConditionTypes.class);
    
    /**
     * 
     */
    private EnumMap<SortOrder, Comparator> comparators = new EnumMap<SortOrder, Comparator>(SortOrder.class) ;
    
    /**
     * 
     */
    private TimeEmitter timer = TimeEmitter.getInstance();
    
    private static final Logger logger = Logger.getLogger(BankImpl.class.getName());

    public BankImpl(String bankName)
    {
	
	conditions.put(ConditionTypes.GROUP1, new GiroAccountConditions(0.5, 11, ConditionTypes.GROUP1));
	conditions.put(ConditionTypes.GROUP2, new GiroAccountConditions(0.5, 11, ConditionTypes.GROUP1));
	conditions.put(ConditionTypes.GROUP3, new GiroAccountConditions(0.5, 11, ConditionTypes.GROUP1));
	
	conditions.put(ConditionTypes.THREE_MONTH, new InvestmentAccountConditions(1.5, ConditionTypes.THREE_MONTH));
	conditions.put(ConditionTypes.SIX_MONTH, new InvestmentAccountConditions(1.5, ConditionTypes.SIX_MONTH));
	conditions.put(ConditionTypes.NINE_MONTH, new InvestmentAccountConditions(1.5, ConditionTypes.NINE_MONTH));
	conditions.put(ConditionTypes.TWELVE_MONTH, new InvestmentAccountConditions(1.5, ConditionTypes.TWELVE_MONTH));
	
	comparators.put(SortOrder.Name, new SortByName());
	comparators.put(SortOrder.Date, new SortByDate());
	comparators.put(SortOrder.Amount, new SortByAmount());
	
	this.bankName = bankName;
    }
    
    public String getBankName()
    {
	return bankName;
    }
    
    @Override
    public void update(Observable o, Object obj)
    {
	if(obj instanceof GiroAccount)
	    payInterest(((Account)obj).getAccountNumber());
	
	else if(obj instanceof InvestmentAccount)
	    ((InvestmentAccount)obj).renewContract();
    }
    
    /***************************************************************************
     * Interface BankCustomerView
     **************************************************************************/
    
    /**
     *
     * @param accountHolder
     * @param accountType
     */
    
    @Override
    public int openInvestmentAccount(String accountHolder, ConditionTypes conditionType, Currency currency)
    {
	accountNumber = accounts.hashCode();
	accounts.put(accountNumber, new InvestmentAccount(accountNumber, 
	accountHolder, currency, (InvestmentAccountConditions)getConditions(conditionType), timer.getCalendar()));
	
	return accountNumber;
    }
    
    @Override
    public int openGiroAccount(String accountHolder, ConditionTypes conditionType, Currency currency)
    {
	accountNumber = accounts.hashCode();
	accounts.put(accountNumber, new GiroAccount(accountNumber,
	accountHolder, currency, (GiroAccountConditions)getConditions(conditionType), timer.getCalendar()));
	
	return accountNumber;
    }
    

    /**
     * 
     * @param accountNumber
     * @return 
     */
    @Override
    public BigInteger getAccountBalance(int accountNumber)
    {
	return accounts.get(accountNumber).getAccountBalance();
    }

    /**
     * 
     * @param accountNumber
     * @param amount
     * @param reasonOfTransfer
     * @throws TransactionNotAllowedException 
     */
    @Override
    public void payInMoney(int accountNumber, double amount, Currency currency, String reasonOfTransfer)
    {
	try
	{
	    Money m = new Money(BigInteger.valueOf((long)(amount * 100)), currency);
	    accounts.get(accountNumber).enterOnCredit(m, reasonOfTransfer);
	}
	catch (SystemNotAvailableException e)
	{
	    logger.info(e.getMessage());
	}
    }

    /**
     * 
     * @param accountNumber
     * @param amount
     * @throws TransactionNotAllowedException 
     */
    @Override
    public void drawMoney(int accountNumber, double amount)
    {
	try
	{
	    Account account = accounts.get(accountNumber);
	    Money m = new Money(BigInteger.valueOf((long)(amount * 100)), account.getCurrency());
	    account.enterOnDebit(m, "draw money");
	}
	catch (SystemNotAvailableException e)
	{
	    logger.info(e.getMessage());
	}
    }

    /**
     * 
     * @param sourceAccount
     * @param targetAccount
     * @param amount
     * @param reasonOfTransfer
     * @throws TransactionNotAllowedException 
     */
    @Override
    public void transferMoney(int sourceAccount, int targetAccount,
	double amount, Currency currency, String reasonOfTransfer)
    {
	try
	{
	    Money m = new Money(BigInteger.valueOf((long)(amount * 100)), currency);
	    
	    accounts.get(sourceAccount).enterOnDebit(m,
		    reasonOfTransfer);
	    
	    accounts.get(targetAccount).enterOnCredit(m,
		    reasonOfTransfer);
	}
		
	catch (SystemNotAvailableException e)
	{
	    logger.info(e.getMessage());
	}
    }
    
    @Override
    public void tranferMoney(String bank, int sourceAccount, int targetAccount,
	double amount, Currency currency, String reasonOfTransfer)
    {
	try
	{
	    Money m = new Money(BigInteger.valueOf((long)(amount * 100)), currency);
	    
	    accounts.get(sourceAccount).enterOnDebit(m,
		    reasonOfTransfer);

	    BankRegistry.getInstance().lookup(bank).payInMoney(targetAccount,
		amount, currency, reasonOfTransfer);
	}
	
	catch (SystemNotAvailableException e)
	{
	    logger.info(e.getMessage());
	}
    }
    
    /**
     * list all accounts
     */
    public void list()
    {
	Collection<Account> collection = accounts.values();
	
	while(collection.iterator().hasNext())
	    System.out.println(collection.iterator().next().toString());
    }

    /**
     * 
     * @param accountNumber
     * @return 
     */
    @Override
    public String getAccountStatement(int accountNumber, SortOrder sortOrder)
    {
	try
	{
	    return bankName + ":\n" + accounts.get(accountNumber).getAccountStatement(comparators.get(sortOrder));
	}
	catch (SystemNotAvailableException e)
	{
	    logger.info(e.getMessage());
	}
	
	return null;
    }
    
    @Override
    public String[] getConditions()
    {
	Collection<Conditions> c = conditions.values();
	String[] condition = new String[conditions.size()];
	
	for(int i = 0; c.iterator().hasNext(); i++, c.iterator().next())
	    condition[i] = c.toString();
	
	return condition;
    }
    
    /***************************************************************************
     * End of interface BankCustomerView
     **************************************************************************/
    
    
    /***************************************************************************
     * Interface BankAdministrationView
     **************************************************************************/

    @Override
    public void addInvestmentCondition(ConditionTypes conditionType, double interest, int contractPeriod)
    {
	conditions.put(conditionType, new InvestmentAccountConditions(interest, conditionType));
    }
    
    @Override
    public void changeGiroInterest(ConditionTypes conditionType, Conditions condition)
    {
	conditions.put(conditionType, condition);
    }
    
    @Override
    public void payInterest(int accountNumber)
    {
	try
	{
	    accounts.get(accountNumber).payInterest();
	}
	catch (NoSuchElementException e)
	{
	    logger.fine(e.getMessage());
	}
	catch (TransactionNotAllowedException e)
	{
	    logger.fine(e.getMessage());
	}
    }
    
    public Conditions getConditions(ConditionTypes conditionType)
    {
	return conditions.get(conditionType);
    }
}
